<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>实现vue双向数据绑定</h1>
        <input type="text" v-model="msg">
        <h3>{{msg}}</h3>
        <h4 v-text="msg"></h4>
        <h4 v-html="msg"></h4>
        <button @click="changeEvent">修改msg</button>
    </div>

    <script>
        class Vue {
            constructor(options) {
                // 通过选择获取根对象
                this.$el = document.querySelector(options.el);
                
                this.$options = options;

                // 代理options的data数据
                this.proxy(options);

                // this.$watchEvent[key] = [event, event, event];
                // 设置一个对象专门保存修改更新的事件
                this.$watchEvent = {};

                // 劫持设置事件
                this.observe();

                // 把view视图与事件进行绑定
                this.compile(this.$el);
            }
            
            proxy(sourceKey, key) {
                // 循环通过set，get方法来实现代理数据
                for (let key in this.$options.data) {
                    Object.defineProperty(this, key, {
                        get: () => {
                            return this.$options.data[key];
                        },
                        set: (val) => {
                            this.$options.data[key] = val;
                        }
                    })
                }
            }

            observe() {
                // 劫持事件
                for (let key in this.$options.data) {
                    let value = this.$options.data[key]
                    let that = this;
                    Object.defineProperty(that.$options.data, key, {
                        get: () => {
                            return value;
                        },
                        set: (val) => {
                            console.log('触发设置事件---')
                            value = val;
                            // 触发以这个key值的更新事件
                            if(that.$watchEvent[key]) {
                                that.$watchEvent[key].forEach((item, index) => {
                                    item.update();
                                });
                            }
                        }
                    })
                }
            }

            compile(cNode) {
                cNode.childNodes.forEach((node, index) => {
                    if (node.nodeType === 1) {
                        // 元素类型
                        if(node.hasAttribute('v-html') || node.hasAttribute('v-text')) {
                            let vmKey = node.getAttribute('v-html') || node.getAttribute('v-text');
                            node.innerHTML = this[vmKey];
                            let wather = new Watch(this, vmKey, node, 'innerHTML');
                            if (this.$watchEvent[vmKey]) {
                                this.$watchEvent[vmKey].push(wather);
                            } else {
                                this.$watchEvent[vmKey] = [];
                                this.$watchEvent[vmKey].push(wather);
                            }
                            // 删除节点事件
                            node.removeAttribute('v-html');
                            node.removeAttribute('v-text');
                        }
                        // 判断是否有v-model
                        if(node.hasAttribute('v-model')) {
                            let vmKey = node.getAttribute('v-model').trim();
                            if (this.hasOwnProperty(vmKey)) {
                                node.value = this[vmKey];
                                let wather = new Watch(this, vmKey, node, 'value');
                                if (this.$watchEvent[vmKey]) {
                                    this.$watchEvent[vmKey].push(wather);
                                } else {
                                    this.$watchEvent[vmKey] = [];
                                    this.$watchEvent[vmKey].push(wather);
                                }
                            }
                            node.addEventListener('input', (event) => {
                                this[vmKey] = node.value;
                            })
                            node.removeAttribute('v-model');
                        }
                        // 判断是否有@click属性
                        if(node.hasAttribute('@click')) {
                            let vmKey = node.getAttribute('@click').trim();
                            node.addEventListener('click', (event) => {
                                this.eventFn = this.$options.methods[vmKey].bind(this);
                                this.eventFn(event);
                            })
                        }
                        if (node.childNodes.length > 0) {
                            this.compile(node);
                        }
                    } else if (node.nodeType === 3) {
                        // 文本类型
                        let reg = /\{\{(.*?)\}\}/g;
                        let text = node.textContent;
                        node.textContent = text.replace(reg, (match, vmKey) => {
                            // 去除空格
                            vmKey = vmKey.trim();
                            if (this.hasOwnProperty(vmKey)) {
                                node.value = this[vmKey];
                                let wather = new Watch(this, vmKey, node, 'textContent');
                                if (this.$watchEvent[vmKey]) {
                                    this.$watchEvent[vmKey].push(wather);
                                } else {
                                    this.$watchEvent[vmKey] = [];
                                    this.$watchEvent[vmKey].push(wather);
                                }
                            }
                            return this[vmKey];
                        })
                    }
                })
            }
        }

        class Watch {
            /**
             * vm：实例化的对象
             * key：绑定的vm触发的属性
             * node：此vm[key]数据绑定的HTML节点
             * property：vm数据所绑定的HTML节点的属性名称
            */
            constructor(vm, key, node, attr) {
                this.vm = vm;
                this.key = key;
                this.node = node;
                this.attr = attr;
            }

            update() {
                this.node[this.attr] = this.vm[this.key];
            }
        }

        let app = new Vue({
            el: '#app',
            data: {
                msg: 'hello world',
                username: '小明'
            },
            methods: {
                changeEvent() {
                    console.log('---changeEvent---')
                    this.msg = 'hello Vue';
                }
            }
        })

        console.log(app);
        
    </script>
</body>
</html>